import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
import * as dat from 'lil-gui'
// import stats from '../common/stats'
import { listenResize, changeColor, randomSection } from '../common/utils'
import { useEffect } from 'react'
import { StarBoard, StarRoute, StarChannel, dicePosiston, homeIndex, gameData, chessData } from './chessMap'

const StarFlyChess = () => {
    console.log('StarFlyChess')
    useEffect(() => {
        console.log('useEffect')
        const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement
        const scene = new THREE.Scene()
        const sizes = {
            width: window.innerWidth,
            height: window.innerHeight,
        }
        const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
        camera.position.set(0, 0, 8)
        const controls = new OrbitControls(camera, canvas)
        // 禁止平移
        const touchColorNum = 70
        // 星星跳跃
        interface starJump {
            // 起点：棋盘序号
            startIndex: number,
            // 终点：棋盘序号
            endIndex: number,
            // 棋子序号
            chessDateIndex: number,
            // 当前跳跃时间
            jumpTime: number,
        }
        let starJumpList: starJump[] = []

        /**
         * Galaxy
         */
        const parameters = {
            isLockCamara: false,
            count: 500, // 粒子数量
            size: 0.02, // 粒子大小
            radius: 3, // 棋子半径
            randomness: 0.2,
            randomnessPower: 4,
            starJumpTime: 500,
            sceneColor: '#000000',
            player0Color: '#ff6030',
            player1Color: '#3de93a',
            player2Color: '#ffbb00',
            player3Color: '#0062ff',
            playerColor: ['#ff3c2e', '#3de93a', '#ffbb00', '#0062ff'],
            playerTouchColor: [changeColor('#ff3c2e', touchColorNum), changeColor('#3de93a', touchColorNum), changeColor('#ffbb00', touchColorNum), changeColor('#0062ff', touchColorNum)],
        }

        scene.background = new THREE.Color(parameters.sceneColor)
        const white = new THREE.Color('#ffffff')

        let galaxyGeometry: THREE.BufferGeometry
        let galaxyMaterial: THREE.PointsMaterial
        let galaxyPoints: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>

        function drawGalaxy() {
            if (galaxyPoints) {
                galaxyGeometry.dispose()
                galaxyMaterial.dispose()
                scene.remove(galaxyPoints)
            }

            // Geometry
            galaxyGeometry = new THREE.BufferGeometry()
            var chessLn = StarBoard.length;
            const position = new Float32Array(parameters.count * (chessLn + 4) * 3)
            const colors = new Float32Array(parameters.count * (chessLn + 4) * 3)
            // 棋盘
            for (let i = 0; i < chessLn; i++) {
                for (let j = 0; j < parameters.count; j++) {
                    const i3 = (i * parameters.count + j) * 3
                    let mixedColor = new THREE.Color(parameters.playerColor[StarBoard[i].player])
                    mixedColor.lerp(white, randomSection(0, 0.1))
                    // xyz轴
                    if (j % 2 == 0) {
                        position[i3] = StarBoard[i].x / 10 - 5 + randomSection(0, 0.3) * (Math.random() < 0.5 ? 1 : -1)
                        position[i3 + 1] = StarBoard[i].y / 10 - 5 + randomSection(0.22, 0.3) * (Math.random() < 0.5 ? 1 : -1)
                    } else {
                        position[i3] = StarBoard[i].x / 10 - 5 + randomSection(0.22, 0.3) * (Math.random() < 0.5 ? 1 : -1)
                        position[i3 + 1] = StarBoard[i].y / 10 - 5 + randomSection(0, 0.3) * (Math.random() < 0.5 ? 1 : -1)
                    }
                    position[i3 + 2] = 0

                    colors[i3] = mixedColor.r
                    colors[i3 + 1] = mixedColor.g
                    colors[i3 + 2] = mixedColor.b
                }
            }
            // 航道
            for (let i = 0; i < 4; i++) {
                for (let j = 0; j < parameters.count; j++) {
                    const i3 = ((i + chessLn) * parameters.count + j) * 3
                    let mixedColor = new THREE.Color(parameters.playerColor[i])
                    if (StarBoard[StarChannel[0][i]].x === StarBoard[StarChannel[1][i]].x) {
                        position[i3] = StarBoard[StarChannel[0][i]].x / 10 - 5 + randomSection(-0.2, 0.2)
                        let y1 = StarBoard[StarChannel[0][i]].y / 10 - 5
                        let y2 = StarBoard[StarChannel[1][i]].y / 10 - 5
                        position[i3 + 1] = randomSection(y1 + (y1 < 0 ? 0.3 : -0.3), y2 + (y2 < 0 ? 0.3 : -0.3))
                        mixedColor.lerp(white, 1 - Math.abs(position[i3 + 1] * 2 / (y1 - y2)))
                    } else {
                        let x1 = StarBoard[StarChannel[0][i]].x / 10 - 5
                        let x2 = StarBoard[StarChannel[1][i]].x / 10 - 5
                        position[i3] = randomSection(x1 + (x1 < 0 ? 0.3 : -0.3), x2 + (x2 < 0 ? 0.3 : -0.3))
                        position[i3 + 1] = StarBoard[StarChannel[0][i]].y / 10 - 5 + randomSection(-0.2, 0.2)
                        mixedColor.lerp(white, 1 - Math.abs(position[i3] * 2 / (x1 - x2)))
                    }
                    position[i3 + 2] = Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * Math.random() * parameters.radius

                    colors[i3] = mixedColor.r
                    colors[i3 + 1] = mixedColor.g
                    colors[i3 + 2] = mixedColor.b
                }
            }
            galaxyGeometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
            galaxyGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))

            // Material
            galaxyMaterial = new THREE.PointsMaterial({
                size: parameters.size,
                sizeAttenuation: true,
                depthWrite: false,
                blending: THREE.AdditiveBlending,
                vertexColors: true,
            })

            galaxyPoints = new THREE.Points(galaxyGeometry, galaxyMaterial)
            scene.add(galaxyPoints)
        }

        let starGeometry: THREE.BufferGeometry
        let starMaterial: THREE.PointsMaterial
        let starPoints: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>

        function drawStar() {
            if (starPoints) {
                starGeometry.dispose()
                starMaterial.dispose()
                scene.remove(starPoints)
            }

            // Geometry
            starGeometry = new THREE.BufferGeometry()
            const position = new Float32Array(parameters.count * 16 * 3)
            const colors = new Float32Array(parameters.count * 16 * 3)
            // 棋子
            for (let i = 0; i < 16; i++) {
                for (let j = 0; j < parameters.count; j++) {
                    const i3 = (i * parameters.count + j) * 3
                    const radius = Math.random() * parameters.radius * (gameData.state === 'fly' && gameData.currentPlayer === chessData[i].player ? 2 : 1 / 2)
                    let mixedColor = new THREE.Color(parameters.playerColor[chessData[i].player])
                    if (chessData[i].state === 'end') {
                        mixedColor.lerp(white, 1)
                    } else if (gameData.touchStar === i) {
                        mixedColor.lerp(new THREE.Color(parameters.playerTouchColor[chessData[i].player]), 1)
                    } else {
                        mixedColor.lerp(white, randomSection(0, 0.1))
                    }
                    let x = StarBoard[chessData[i].index].x
                    let y = StarBoard[chessData[i].index].y
                    let z = 0
                    // 判断是否进入跳跃动画
                    for (let k = 0, kln = starJumpList.length; k < kln; k++) {
                        if (i === starJumpList[k].chessDateIndex) {
                            let startStar = StarBoard[starJumpList[k].startIndex]
                            let endStar = StarBoard[starJumpList[k].endIndex]
                            x = startStar.x - (startStar.x - endStar.x) * starJumpList[k].jumpTime / parameters.starJumpTime
                            y = startStar.y - (startStar.y - endStar.y) * starJumpList[k].jumpTime / parameters.starJumpTime
                            // z = 0.98 * (starJumpList[k].jumpTime ** 2)/2
                            starJumpList[k].jumpTime++
                            if (starJumpList[k].jumpTime === parameters.starJumpTime) {
                                starJumpList.splice(k, 1)
                            }
                            break
                        }
                    }
                    // xyz轴
                    position[i3] = x / 10 - 5 + Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * radius
                    position[i3 + 1] = y / 10 - 5 + Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * radius
                    position[i3 + 2] = z + Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * radius / 3

                    colors[i3] = mixedColor.r
                    colors[i3 + 1] = mixedColor.g
                    colors[i3 + 2] = mixedColor.b
                }
            }
            starGeometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
            starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))

            // Material
            starMaterial = new THREE.PointsMaterial({
                size: parameters.size,
                sizeAttenuation: true,
                depthWrite: false,
                blending: THREE.AdditiveBlending,
                vertexColors: true,
            })

            starPoints = new THREE.Points(starGeometry, starMaterial)
            scene.add(starPoints)
        }

        let diceGeometry: THREE.BufferGeometry
        let diceMaterial: THREE.PointsMaterial
        let dicePoints: THREE.Points<THREE.BufferGeometry, THREE.PointsMaterial>

        function drawDice() {
            if (dicePoints) {
                diceGeometry.dispose()
                diceMaterial.dispose()
                scene.remove(dicePoints)
            }
            // Geometry
            diceGeometry = new THREE.BufferGeometry()
            const position = new Float32Array(parameters.count * 6 * 3)
            const colors = new Float32Array(parameters.count * 6 * 3)
            if (gameData.state === 'dice') {
                if (gameData.dice++ > 5) {
                    gameData.dice = 1
                }
            }
            let dice = gameData.dice
            for (let i = 0; i < dice; i++) {
                for (let j = 0; j < parameters.count; j++) {
                    const i3 = (i * parameters.count + j) * 3
                    const radius = Math.random() * parameters.radius
                    let mixedColor = new THREE.Color(parameters.playerColor[gameData.currentPlayer])
                    mixedColor.lerp(white, randomSection(0, 0.2))
                    // xyz轴
                    position[i3] = dicePosiston[gameData.currentPlayer].x + (i > 2 ? 0.4 * i - 1.2 : 0.4 * i)
                        + Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * radius / 5
                    position[i3 + 1] = dicePosiston[gameData.currentPlayer].y + (i > 2 ? -0.4 : 0)
                        + Math.random() ** parameters.randomnessPower
                        * (Math.random() < 0.5 ? 1 : -1)
                        * parameters.randomness
                        * radius / 5
                    position[i3 + 2] = 0

                    colors[i3] = mixedColor.r
                    colors[i3 + 1] = mixedColor.g
                    colors[i3 + 2] = mixedColor.b
                }
            }
            diceGeometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
            diceGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))

            // Material
            diceMaterial = new THREE.PointsMaterial({
                size: parameters.size,
                sizeAttenuation: true,
                depthWrite: false,
                blending: THREE.AdditiveBlending,
                vertexColors: true,
            })

            dicePoints = new THREE.Points(diceGeometry, diceMaterial)
            scene.add(dicePoints)
        }

        let roundGeometry: THREE.BufferGeometry
        let roundMesh: THREE.Mesh<THREE.BufferGeometry, THREE.MeshBasicMaterial>
        function drawRoundText() {
            if (dicePoints) {
                roundGeometry.dispose()
                scene.remove(roundMesh)
            }
            let roundLoader = new FontLoader()
            roundLoader.load('./fonts/gentilis_bold.typeface.json', function (font) {
                roundGeometry = new TextGeometry('Round : ' + gameData.round, {
                    font: font,
                    size: 3,
                    height: 0.5,
                    curveSegments: 4,
                    bevelEnabled: true,
                    bevelThickness: 0.1,
                    bevelSize: 0.1,
                    bevelSegments: 3
                });

                let roundMaterial = new THREE.MeshBasicMaterial({
                    color: parameters.playerColor[gameData.currentPlayer],
                });
                roundMaterial.transparent = true
                roundMaterial.opacity = 0.8
                roundMesh = new THREE.Mesh(roundGeometry, roundMaterial)
                roundMesh.scale.set(0.3, 0.3, 0.3)
                roundMesh.position.set(-3, 5.2, -0.1)
                scene.add(roundMesh)
            })
        }
        drawRoundText()

        // 确定鼠标在棋子上的行动
        function checkMouseMove(event: MouseEvent) {
            if (event.type === 'mouseup' && gameData.state === 'dice') {
                rollDice()
                return
            }
            // mouse的坐标以屏幕左上角为原点，x向右，y向下；canvas的坐标以屏幕中央为原点，x向右，y向上；
            // let mousex = (event.clientX - canvas.clientWidth / 2) / (canvas.clientHeight / 2)
            //     * (camera.position.z / -Math.tan(90 - camera.getEffectiveFOV() / 2))
            // let mousey = -(event.clientY - canvas.clientHeight / 2) / (canvas.clientHeight / 2)
            //     * (camera.position.z / -Math.tan(90 - camera.getEffectiveFOV() / 2))
            gameData.touchStar = -1

            // 屏幕坐标--标准坐标--世界坐标
            let normalPos = new THREE.Vector3()
            let worldPos = new THREE.Vector3()
            let targetZ = 0
            normalPos.set((event.clientX / canvas.clientWidth) * 2 - 1, -(event.clientY / canvas.clientHeight) * 2 + 1, targetZ)
            normalPos.unproject(camera)
            normalPos.sub(camera.position).normalize()
            let distance = (targetZ - camera.position.z) / normalPos.z;
            worldPos.copy(camera.position).add(normalPos.multiplyScalar(distance))

            for (let i = 0; i < 16; i++) {
                let mapx = StarBoard[chessData[i].index].x / 10 - 5
                let mapy = StarBoard[chessData[i].index].y / 10 - 5
                // console.log(i + " : " + worldPos.x + "," + worldPos.y + " " + mapx + "," + mapy)
                if (worldPos.x < mapx + 0.4 && worldPos.x > mapx - 0.4 && worldPos.y < mapy + 0.4 && worldPos.y > mapy - 0.4) {
                    gameData.touchStar = i
                    // console.log(i+ " : "+mousex + "," + mousey + " " + mapx + "," + mapy)
                    if (event.type === 'mouseup' && gameData.state === 'fly') {
                        gameData.currentStar = i
                        starMove(i)
                    }
                }
            }
        }

        function rollDice() {
            gameData.dice = Math.floor(randomSection(1, 7))
            // 开局加个保底
            if (gameData.round < 1) {
                let homeStarNum = 0
                for (let i = 0; i < 4; i++) {
                    if (chessData[gameData.currentPlayer * 4 + i].state === 'home') {
                        homeStarNum++
                    }
                }
                if (homeStarNum > 3) {
                    gameData.dice = 6
                }
            }
            gameData.state = 'fly'
            cancelAnimationFrame(diceAniId)
            drawDice()
            console.log("投掷完毕，等待p" + gameData.currentPlayer + "行棋")
            // 骰子点数小于6，要判断四颗棋子是不是全在老家，是则跳过
            if (gameData.dice < 6) {
                let cantMoveNum = 0
                for (let i = 0; i < 4; i++) {
                    if (chessData[gameData.currentPlayer * 4 + i].state === 'home' || chessData[gameData.currentPlayer * 4 + i].state === 'end') {
                        cantMoveNum++
                    }
                }
                if (cantMoveNum > 3) {
                    nextPlayer(0)
                    console.log("p" + gameData.currentPlayer + "不可行棋")
                }
            }
        }

        // 棋子行动
        function starMove(currentStar: number) {
            if (chessData[currentStar].player !== gameData.currentPlayer || chessData[currentStar].state === 'end') {
                console.log("棋子不可行动")
                return
            }
            let startIndex = chessData[currentStar].index
            if (chessData[currentStar].state === 'home') {
                if (gameData.dice === 6) {
                    chessData[currentStar].index = StarRoute[gameData.currentPlayer][0]
                    chessData[currentStar].state = 'start'
                    gameData.state = 'dice'
                    diceAnimations()
                    console.log("等待p" + gameData.currentPlayer + "再次投骰子")
                } else {
                    console.log("棋子不可行动")
                    return
                }
            } else {
                let ln = StarRoute[gameData.currentPlayer].length
                let routeIndex = StarRoute[gameData.currentPlayer].indexOf(chessData[currentStar].index) + gameData.dice
                chessData[currentStar].state = 'fly'
                if (routeIndex >= ln) {
                    chessData[currentStar].state = 'end'
                    chessData[currentStar].index = homeIndex[currentStar]
                } else {
                    chessData[currentStar].index = StarRoute[gameData.currentPlayer][routeIndex]
                    // 飞跃航道+同色格子跳跃，终点门口不用跳
                    if (chessData[currentStar].index !== 49 && chessData[currentStar].index !== 36 && chessData[currentStar].index !== 23 && chessData[currentStar].index !== 10) {
                        if (chessData[currentStar].index === StarChannel[0][chessData[currentStar].player]) {
                            // 先飞后跳
                            chessData[currentStar].index = StarChannel[1][chessData[currentStar].player]
                            if (chessData[currentStar].index < 52 && StarBoard[chessData[currentStar].index].player === chessData[currentStar].player) {
                                chessData[currentStar].index = StarRoute[gameData.currentPlayer][StarRoute[gameData.currentPlayer].indexOf(chessData[currentStar].index) + 4]
                            }
                        } else if (chessData[currentStar].index < 52 && StarBoard[chessData[currentStar].index].player === chessData[currentStar].player) {
                            // 先跳后飞
                            chessData[currentStar].index = StarRoute[gameData.currentPlayer][StarRoute[gameData.currentPlayer].indexOf(chessData[currentStar].index) + 4]
                            if (chessData[currentStar].index === StarChannel[0][chessData[currentStar].player]) {
                                chessData[currentStar].index = StarChannel[1][chessData[currentStar].player]
                            }
                        }
                    }
                }
                // 判断是否击毁敌机
                for (let i = 0; i < 16; i++) {
                    if (chessData[i].player !== gameData.currentPlayer && chessData[i].index === chessData[currentStar].index) {
                        console.log("p" + gameData.currentPlayer + "击毁p" + chessData[i].player + "的棋子")
                        chessData[i].index = homeIndex[i]
                        chessData[i].state = 'home'
                    }
                }
                if (gameData.dice < 6 || isEnd()) {
                    nextPlayer(0)
                } else {
                    gameData.state = 'dice'
                    diceAnimations()
                    console.log("等待p" + gameData.currentPlayer + "再次投骰子")
                }
            }
            let starJump = {
                startIndex: startIndex,
                endIndex: chessData[currentStar].index,
                chessDateIndex: currentStar,
                jumpTime: 0,
            }
            starJumpList.push(starJump)
            drawStar()
        }

        function isEnd() {
            let endStarNum = 0
            for (let i = 0; i < 4; i++) {
                if (chessData[gameData.currentPlayer * 4 + i].state === 'end') {
                    endStarNum++
                }
            }
            if (endStarNum > 3) {
                return true
            }
            return false
        }

        function nextPlayer(times: number) {
            if (times > 3) {
                console.log("游戏结束！")
                gameData.state = 'end'
            }
            gameData.state = 'dice'
            diceAnimations()
            gameData.currentPlayer++
            if (gameData.currentPlayer > 3) {
                gameData.currentPlayer = 0
                gameData.round++
            }
            if (isEnd()) {
                nextPlayer(times)
            }
            drawRoundText()
            console.log("等待p" + gameData.currentPlayer + "投骰子")
        }

        // 设置监听事件
        const listenMouseMove = (element: HTMLElement) => {
            element.addEventListener('mousemove', checkMouseMove)
            element.addEventListener('mouseup', checkMouseMove)
        }

        drawGalaxy()
        // drawStar()
        gameData.state = 'dice'

        // Renderer
        const renderer = new THREE.WebGLRenderer({
            canvas,
        })
        renderer.setSize(sizes.width, sizes.height)
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

        listenResize(sizes, camera, renderer)
        // dbClkfullScreen(document.body)
        listenMouseMove(canvas)

        // 坐标系，红X绿Y蓝Z
        // var axes = new THREE.AxesHelper(100);
        // scene.add(axes);

        // Animations
        let starTime = 0
        let starAniId: number
        const starAnimations = () => {
            starAniId = requestAnimationFrame(starAnimations)
            if (starTime === 10) {
                starTime = 0
                drawStar()
            }
            starTime++
            renderer.render(scene, camera)
        }
        starAnimations()

        let diceTime = 0
        let diceAniId: number
        const diceAnimations = () => {
            if (gameData.state !== 'dice') {
                cancelAnimationFrame(diceAniId)
                return
            }
            diceAniId = requestAnimationFrame(diceAnimations)
            if (diceTime === 15) {
                diceTime = 0
                drawDice()
            }
            diceTime++
            renderer.render(scene, camera)
        }
        diceAnimations()

        /**
         * Debug
         */
        const initGUI = (fn: Function) => {
            const gui = new dat.GUI()
            gui.add(parameters, 'isLockCamara', 100, 100000, 100).onFinishChange(() => {
                if (parameters.isLockCamara) {
                    controls.enableRotate = false
                    controls.reset()
                } else {
                    controls.enableRotate = true
                }
            })
            gui.add(parameters, 'count', 100, 100000, 100).onFinishChange(fn)
            gui.add(parameters, 'size', 0.001, 0.1, 0.001).onFinishChange(fn)
            gui.add(parameters, 'radius', 0.01, 20, 0.01).onFinishChange(fn)
            gui.add(parameters, 'randomness', 0, 2, 0.001).onFinishChange(fn)
            gui.add(parameters, 'randomnessPower', 1, 10, 0.001).onFinishChange(fn)
            gui.add(parameters, 'starJumpTime', 100, 10000, 1)
            gui.addColor(parameters, 'sceneColor').onFinishChange(() => {
                scene.background = new THREE.Color(parameters.sceneColor)
                drawGalaxy()
            })
            gui.addColor(parameters, 'player0Color').onFinishChange(() => {
                parameters.playerColor[0] = parameters.player0Color
                parameters.playerTouchColor[0] = changeColor(parameters.player0Color, touchColorNum)
                console.log(parameters.playerTouchColor[0])
                drawGalaxy()
            })
            gui.addColor(parameters, 'player1Color').onFinishChange(() => {
                parameters.playerColor[1] = parameters.player1Color
                parameters.playerTouchColor[1] = changeColor(parameters.player1Color, touchColorNum)
                drawGalaxy()
            })
            gui.addColor(parameters, 'player2Color').onFinishChange(() => {
                parameters.playerColor[2] = parameters.player2Color
                parameters.playerTouchColor[2] = changeColor(parameters.player2Color, touchColorNum)
                drawGalaxy()
            })
            gui.addColor(parameters, 'player3Color').onFinishChange(() => {
                parameters.playerColor[3] = parameters.player0Color
                parameters.playerTouchColor[3] = changeColor(parameters.player3Color, touchColorNum)
                drawGalaxy()
            })
            gui.close()
        }
        initGUI(drawGalaxy)

        return () => {
            console.log('清理绑定事件、动画')
            canvas.removeEventListener('mousemove', checkMouseMove);
            canvas.removeEventListener('mouseup', checkMouseMove);
            cancelAnimationFrame(starAniId)
            cancelAnimationFrame(diceAniId)
            renderer.clear
        }
    }, []);

    return (
        <canvas id="mainCanvas" className="webgl"></canvas>
    )
}

export default StarFlyChess;